﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Xml;
using System.IO;
using System.Diagnostics;

namespace Henu.TinyTools
{
    /// <summary>
    /// 工具窗口
    /// </summary>
    public partial class FrmMapTool : Form
    {
        #region 构造和初始化
        /// <summary>
        /// 构造和初始化
        /// </summary>
        public FrmMapTool()
        {
            InitializeComponent();
            rtxbContent.AllowDrop = true;
            rtxbContent.DragDrop += rtxbContent_DragDrop;
            // 文件过滤
            openFileDialog.Filter = "文本文件|*.txt;*.log;*.lg|All|*.*";
            saveFileDialog.Filter = "KML文件|*.kml|XML文件|*.xml|All|*.*";
        }
        #endregion

        #region 用户事件
        /// <summary>
        /// 用户事件
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnGenerate_Click(object sender, EventArgs e)
        {
            GenerateFile();

        }
        /// <summary>
        /// 生成文件
        /// </summary>
        private void GenerateFile()
        {
            try
            {
                if (string.IsNullOrEmpty(rtxbContent.Text))
                    return;
                if (string.IsNullOrEmpty(saveFileDialog.FileName))
                {
                    saveFileDialog.ShowDialog();
                    return;
                }
                bool fileCompleted;
                if (checkBoxLine.Checked)
                {
                    fileCompleted = GeneranateLine();
                }
                else
                {
                    fileCompleted = GeneranatePoints();
                }

                // 处理完成
                if (fileCompleted)
                {
                    if (finError)
                    {
                        string cmdSql = "/select," + _errFilePath;
                        System.Diagnostics.ProcessStartInfo psInfo = new System.Diagnostics.ProcessStartInfo("explorer", cmdSql);
                        System.Diagnostics.Process.Start(psInfo);
                    }
                    else
                    {
                        string cmdSql = "/select," + txtTargetFile.Text.Trim();
                        System.Diagnostics.ProcessStartInfo psInfo = new System.Diagnostics.ProcessStartInfo("explorer", cmdSql);
                        System.Diagnostics.Process.Start(psInfo);
                    }

                    // 清空文件选择
                    txtSourceFile.Text = string.Empty;
                    txtTargetFile.Text = string.Empty;
                    openFileDialog.FileName = string.Empty;
                    saveFileDialog.FileName = string.Empty;
                    rtxbContent.Clear();
                    _sourceFile = null;
                }



            }
            catch (Exception ex)
            {

                throw ex;
            }
        }
        /// <summary>
        /// 设置错误文件的位置
        /// </summary>
        /// <param name="p"></param>
        /// <returns></returns>
        private string GetErrorFilePath(string filePath)
        {
            FileInfo file = new FileInfo(filePath);
            string cource = file.FullName.Replace(file.Extension, "_Error.txt");
            return cource;
        }
        #endregion

        #region 解析生成文件
        /// <summary>
        /// 生成坐标点集合
        /// </summary>
        private bool GeneranatePoints()
        {
            List<GPGGAData> DataLines = ReadContentLines();
            if (DataLines == null)
                return false;
            if (DataLines.Count < 1)
            {
                MessageBox.Show("没有有效的数据");
                return false;
            }


            XmlDocument xmlDoc = new XmlDocument();

            XmlElement nodeKML = xmlDoc.CreateElement("kml");
            nodeKML.SetAttribute("xmlns", "http://earth.google.com/kml/2.0");
            xmlDoc.AppendChild(nodeKML);
            XmlElement nodeFolder = xmlDoc.CreateElement("Folder");
            nodeKML.AppendChild(nodeFolder);

            int rowNO = 0;
            foreach (var item in DataLines)
            {
                rowNO++;
                decimal longitudeValue = item.Longitude;
                decimal latitudeValue = item.Latitude;

                XmlElement Placemark = xmlDoc.CreateElement("Placemark");

                string nodeXML = @" <description>{1},{2}{3}{4}</description>
      <name>{0}</name>
      <LookAt>
        <longitude>{1}</longitude>
        <latitude>{2}</latitude>
        <range>20000</range>
        <tilt>0</tilt>
        <heading>3</heading>
      </LookAt>
      <Point>
        <coordinates>{1},{2},0</coordinates>
      </Point>";

                Placemark.InnerXml = string.Format(nodeXML, rowNO, longitudeValue, latitudeValue, Environment.NewLine, item.UTCTime);


                nodeFolder.AppendChild(Placemark);
            }
            xmlDoc.Save(txtTargetFile.Text.Trim());

            return true;
        }
        /// <summary>
        /// 发现错误
        /// </summary>
        bool finError = false;
        /// <summary>
        /// 读取数据的有效行
        /// </summary>
        /// <returns></returns>
        private List<GPGGAData> ReadContentLines()
        {
            finError = false;
            _errFilePath = GetErrorFilePath(txtTargetFile.Text.Trim());
            int rowIndex = 0;
            try
            {
                //$GPGGA：起始引导符及语句格式说明(本句为GPS定位数据)；
                //<1> UTC时间，格式为hhmmss.sss；
                //<2> 纬度，格式为ddmm.mmmm(第一位是零也将传送)；
                //<3> 纬度半球，N或S(北纬或南纬)
                //<4> 经度，格式为dddmm.mmmm(第一位零也将传送)；
                //<5> 经度半球，E或W(东经或西经)
                //<6> GPS状态， 0初始化， 1单点定位， 2码差分， 3无效PPS， 4固定解， 5浮点解， 6正在估算 7，人工输入固定值， 8模拟模式， 9WAAS查分
                //<7> 使用卫星数量，从00到12(第一个零也将传送)
                //<8> 水平精度因子，0.5到99.9
                //<9> 天线离海平面的高度，-9999.9到9999.9米
                //M 指单位米
                //<10> 大地水准面高度，-9999.9到9999.9米
                //M 指单位米
                //<11> 差分GPS数据期限(RTCM SC-104)，最后设立RTCM传送的秒数量，如不是差分定位则为空
                //<12> 差分参考基站标号，从0000到1023(首位0也将传送)。
                //* 语句结束标志符
                List<GPGGAData> list = new List<GPGGAData>();

                foreach (var item in rtxbContent.Lines)
                {
                    rowIndex++;
                    try
                    {
                        if (!item.Contains("$GPGGA"))
                        {
                            LogDataError(item, rowIndex);
                            continue;
                        }

                        string line = item.Substring(item.IndexOf("$GPGGA")).Trim();
                        if (string.IsNullOrEmpty(line))
                        {
                            LogDataError(item, rowIndex);
                            continue;
                        }
                        string[] cells = line.Split(',');
                        if (cells.Length != 15)
                        {
                            LogDataError(item, rowIndex);
                            continue;
                        }
                        GPGGAData data = new GPGGAData();
                        data.UTCTime = DateTime.ParseExact(cells[1], "HHmmss.ff", null).AddHours(8);
                        data.Latitude = GetLongitude_LatitudeValue(cells[2]);
                        data.Longitude = GetLongitude_LatitudeValue(cells[4]);
                        list.Add(data);
                    }
                    catch (Exception ex)
                    {
                        LogDataError(item, rowIndex);
                        throw ex;
                    }

                }

                return list;
            }
            catch (Exception ex)
            {

                MessageBox.Show("在" + rowIndex + " 行遇到异常: " + ex.Message);
            }
            return null;
        }
        /// <summary>
        /// 记录解析错误的数据
        /// </summary>
        string _errFilePath = "";
        /// <summary>
        /// 记录解析错误的数据
        /// </summary>
        /// <param name="item"></param>
        /// <param name="rowIndex"></param>
        private void LogDataError(string item, int rowIndex)
        {
            int leftPos = (int)Math.Ceiling(Math.Log10(rtxbContent.Lines.Length));
            string formatstr = "在第 {0:D" + leftPos.ToString() + "} 行\t{1}\r\n";
            File.AppendAllText(_errFilePath, string.Format(formatstr, rowIndex, item));
        }
        /// <summary>
        /// 生成线
        /// </summary>
        private bool GeneranateLine()
        {
            try
            {
                List<GPGGAData> mDataLines = ReadContentLines();
                if (mDataLines == null)
                    return false;
                if (mDataLines.Count < 1)
                {
                    MessageBox.Show("没有有效的数据");
                    return false;
                }

                XmlDocument xmlDoc = new XmlDocument();

                XmlElement nodeKML = xmlDoc.CreateElement("kml");
                nodeKML.SetAttribute("xmlns", "http://earth.google.com/kml/2.0");
                xmlDoc.AppendChild(nodeKML);
                XmlElement nodeDocument = xmlDoc.CreateElement("Document");
                nodeKML.AppendChild(nodeDocument);
                XmlElement Placemark = xmlDoc.CreateElement("Placemark");
                nodeDocument.AppendChild(Placemark);

                XmlElement name = xmlDoc.CreateElement("name");
                name.InnerText = _sourceFile.Name.Replace(_sourceFile.Extension, string.Empty);
                Placemark.AppendChild(name);
                XmlElement Style = xmlDoc.CreateElement("Style");
                Placemark.AppendChild(Style);
                XmlElement LineString = xmlDoc.CreateElement("LineString");
                Placemark.AppendChild(LineString);

                XmlElement LineStyle = xmlDoc.CreateElement("LineStyle");
                Style.AppendChild(LineStyle);
                XmlElement color = xmlDoc.CreateElement("color");
                color.InnerText = "ff0000ff";
                LineStyle.AppendChild(color);
                XmlElement width = xmlDoc.CreateElement("width");
                width.InnerText = "5";
                LineStyle.AppendChild(width);
                Placemark.AppendChild(LineString);

                XmlElement coordinates = xmlDoc.CreateElement("coordinates");
                LineString.AppendChild(coordinates);
                StringBuilder pointSB = new StringBuilder();
                foreach (var item in mDataLines)
                {
                    pointSB.Append(string.Format("{0},{1}  ", item.Longitude, item.Latitude));
                }
                coordinates.InnerText = pointSB.ToString();

                xmlDoc.Save(txtTargetFile.Text.Trim());

                return true;
            }
            catch (Exception ex)
            {

                throw ex;
            }
        }
        #endregion

        #region 绘制行号
        /// <summary>
        /// 显示行号
        /// </summary>
        private void ShowContentLineNo()
        {
            // 根据位置获取行号
            Point p = this.rtxbContent.Location;
            int crntFirstIndex = this.rtxbContent.GetCharIndexFromPosition(p);
            int crntFirstLine = this.rtxbContent.GetLineFromCharIndex(crntFirstIndex);
            Point crntFirstPos = this.rtxbContent.GetPositionFromCharIndex(crntFirstIndex);
            // 获取最后一行的行号
            p.Y += this.rtxbContent.Height;
            int crntLastIndex = this.rtxbContent.GetCharIndexFromPosition(p);
            int crntLastLine = this.rtxbContent.GetLineFromCharIndex(crntLastIndex);
            Point crntLastPos = this.rtxbContent.GetPositionFromCharIndex(crntLastIndex);

            //准备画图
            Graphics g = this.panelContent.CreateGraphics();
            Font font = new Font(this.rtxbContent.Font, this.rtxbContent.Font.Style);
            SolidBrush brush = new SolidBrush(Color.Green);

            // 计算行号应该占用的宽度
            float hFont = g.MeasureString(crntLastLine.ToString(), font).Width;
            panelContent.Padding = new System.Windows.Forms.Padding(Convert.ToInt32(Math.Ceiling(hFont)), 0, 0, 0);

            //画图开始
            //刷新画布
            Rectangle rect = this.panelContent.ClientRectangle;
            brush.Color = this.panelContent.BackColor;
            g.FillRectangle(brush, 0, 0, this.panelContent.Padding.Left, this.panelContent.ClientRectangle.Height);
            //重置画笔颜色
            brush.Color = Color.Green;

            //绘制行号
            int lineSpace = 0;
            if (crntFirstLine != crntLastLine)
            {
                lineSpace = (crntLastPos.Y - crntFirstPos.Y) / (crntLastLine - crntFirstLine);
            }
            else
            {
                lineSpace = Convert.ToInt32(this.rtxbContent.Font.Size);
            }


            int brushY = crntLastPos.Y + Convert.ToInt32(font.Size * 0.20f);
            for (int i = crntLastLine; i >= crntFirstLine; i--)
            {
                g.DrawString((i + 1).ToString(), font, brush, 0, brushY);
                brushY -= lineSpace;
            }
            g.Dispose();
            font.Dispose();
            brush.Dispose();
        }
        /// <summary>
        /// 更新行号
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void panelContent_Paint(object sender, PaintEventArgs e)
        {
            try
            {
                ShowContentLineNo();
            }
            catch (Exception ex)
            {

                throw ex;
            }
        }
        private void rtxbContent_VScroll(object sender, EventArgs e)
        {
            panelContent.Invalidate();
        }
        #endregion

        #region 单选控制
        /// <summary>
        /// 单选控制
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void checkBoxLine_CheckedChanged(object sender, EventArgs e)
        {
            checkBoxPoint.Checked = !checkBoxLine.Checked;
        }
        /// <summary>
        /// 单选控制
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void checkBoxPoint_CheckedChanged(object sender, EventArgs e)
        {
            checkBoxLine.Checked = !checkBoxPoint.Checked;
        }
        #endregion

        #region  GPGGAData 格式对象
        /// <summary>
        /// GPGGA 数据
        /// </summary>
        public class GPGGAData
        {
            //$GPGGA：起始引导符及语句格式说明(本句为GPS定位数据)；
            //<1> UTC时间，格式为hhmmss.sss；
            public DateTime UTCTime { get; set; }
            /// <summary>
            /// &lt;2> 纬度，格式为ddmm.mmmm(第一位是零也将传送)；
            /// </summary>
            public decimal Latitude { get; set; }
            //<3> 纬度半球，N或S(北纬或南纬)
            public string LatitudePart { get; set; }
            /// <summary>
            /// &lt;4> 经度，格式为dddmm.mmmm(第一位零也将传送)；
            /// </summary>
            public decimal Longitude { get; set; }
            //<5> 经度半球，E或W(东经或西经)
            public string LongitudePart { get; set; }
            //<6> GPS状态， 0初始化， 1单点定位， 2码差分， 3无效PPS， 4固定解， 5浮点解， 6正在估算 7，人工输入固定值， 8模拟模式， 9WAAS查分
            public Byte State { get; set; }
            //<7> 使用卫星数量，从00到12(第一个零也将传送)
            //<8> 水平精度因子，0.5到99.9
            //<9> 天线离海平面的高度，-9999.9到9999.9米
            //M 指单位米
            //<10> 大地水准面高度，-9999.9到9999.9米
            public float WaterLevel { get; set; }
            //M 指单位米
            //<11> 差分GPS数据期限(RTCM SC-104)，最后设立RTCM传送的秒数量，如不是差分定位则为空
            //<12> 差分参考基站标号，从0000到1023(首位0也将传送)。
            //* 语句结束标志符

            /// <summary>
            /// 备用的字段
            /// </summary>
            public string Mark { get; set; }
            /// <summary>
            /// 误差距离
            /// </summary>
            public decimal Distance { get; set; }
        }
        /// <summary>   
        /// 获取经纬度decimal值    dddmm.mmmm
        /// </summary>   
        /// <param name="value"></param>   
        /// <returns></returns>   
        public decimal GetLongitude_LatitudeValue(string value)
        {
            try
            {
                decimal source = Convert.ToDecimal(value);
                decimal ds = Math.Floor(source / 100);
                return Math.Round(ds + (source - ds * 100) / 60, 6);
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
            return -1;
        }
        #endregion

        #region 文件选择处理
        /// <summary>
        /// 源文件
        /// </summary>
        private FileInfo _sourceFile = null;
        /// <summary>
        /// 支持拖放
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void rtxbContent_DragDrop(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                string strFile = ((System.Array)e.Data.GetData(DataFormats.FileDrop)).GetValue(0).ToString();
                if (File.Exists(strFile))
                {
                    LoadSourceFile(strFile);
                }
            }
            else if (e.Data.GetDataPresent(DataFormats.Text))
            {
                rtxbContent.Clear();
                rtxbContent.Text = e.Data.GetData(DataFormats.Text).ToString();
            }
        }
        private void btnSourceFile_Click(object sender, EventArgs e)
        {
            openFileDialog.ShowDialog();
        }

        private void btnTargetFile_Click(object sender, EventArgs e)
        {
            saveFileDialog.ShowDialog();
        }

        private void txtSourceFile_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            openFileDialog.ShowDialog();
        }

        private void txtTargetFile_MouseDoubleClick(object sender, MouseEventArgs e)
        {

            saveFileDialog.ShowDialog();
        }
        /// <summary>
        /// 选择文件完成
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void saveFileDialog_FileOk(object sender, CancelEventArgs e)
        {
            // 如果目录不存在则创建
            FileInfo selFile = new FileInfo(saveFileDialog.FileName);
            if (!Directory.Exists(selFile.DirectoryName))
                Directory.CreateDirectory(selFile.DirectoryName);
            if (!string.IsNullOrEmpty(openFileDialog.FileName) && !string.IsNullOrEmpty(saveFileDialog.FileName))
                GenerateFile();
        }

        private void openFileDialog_FileOk(object sender, CancelEventArgs e)
        {
            if (!string.IsNullOrEmpty(openFileDialog.FileName))
                LoadSourceFile(openFileDialog.FileName);

        }

        private void LoadSourceFile(string filePath)
        {
            txtSourceFile.Text = filePath;
            saveFileDialog.FileName = CheckTargrtFileAvailable(filePath);
            txtTargetFile.Text = saveFileDialog.FileName;
            rtxbContent.Clear();
            rtxbContent.Lines = File.ReadAllLines(filePath);
        }

        private string CheckTargrtFileAvailable(string fileName)
        {
            _sourceFile = new FileInfo(fileName);
            string filepath = string.Format("{0}\\{1}.kml", _sourceFile.DirectoryName,
                          _sourceFile.Name.Replace(_sourceFile.Extension, string.Empty));
            if (_sourceFile.Exists)
            {
                filepath = string.Format("{0}\\{1}_{2}.kml", _sourceFile.DirectoryName,
                          _sourceFile.Name.Replace(_sourceFile.Extension, string.Empty), DateTime.Now.ToString("yyyyMMHHmm"));
            }
            return filepath;
        }
        #endregion

        #region 平均值和最大最小值
        /// <summary>
        /// 平均值和最大最小值
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnDiffValue_Click(object sender, EventArgs e)
        {

            if (string.IsNullOrEmpty(saveFileDialog.FileName))
            {
                if (saveFileDialog.ShowDialog() != System.Windows.Forms.DialogResult.OK)
                    return;
            }
            List<GPGGAData> mDataLines = ReadContentLines();

            if (mDataLines.Count < 3)
            {
                MessageBox.Show("没有足够有效的数据");
                return;
            }

            //decimal avageLatitude = mDataLines.Average(p => p.Latitude);
            //decimal avageLongitude = mDataLines.Average(p => p.Longitude);

            decimal avageLatitude = 0.0M;
            decimal avageLongitude = 0.0M;
            CalculateAverage(mDataLines, out avageLatitude, out avageLongitude);

            GPGGAData avgePoint = new GPGGAData() { Latitude = avageLatitude, Longitude = avageLongitude };

            GPGGAData minPoint = null;
            GPGGAData maxPoint = null;
            decimal minDis = decimal.MaxValue;
            decimal maxDis = decimal.MinValue;

            foreach (var item in mDataLines)
            {
                decimal distance = Distance(avgePoint, item);
                if (distance < minDis)
                {
                    minDis = distance;
                    minPoint = item;
                }

                if (distance > maxDis)
                {
                    maxDis = distance;
                    maxPoint = item;
                }
            }

            avgePoint.Mark = "平均";
            maxPoint.Mark = "最大";
            maxPoint.Distance = maxDis;

            minPoint.Mark = "最小";
            minPoint.Distance = minDis;

            List<GPGGAData> DataLines = new List<GPGGAData>() { avgePoint, maxPoint, minPoint };

            XmlDocument xmlDoc = new XmlDocument();

            XmlElement nodeKML = xmlDoc.CreateElement("kml");
            nodeKML.SetAttribute("xmlns", "http://earth.google.com/kml/2.0");
            xmlDoc.AppendChild(nodeKML);
            XmlElement nodeFolder = xmlDoc.CreateElement("Folder");
            nodeKML.AppendChild(nodeFolder);

            int rowNO = 0;
            foreach (var item in DataLines)
            {
                rowNO++;
                decimal longitudeValue = item.Longitude;
                decimal latitudeValue = item.Latitude;

                XmlElement Placemark = xmlDoc.CreateElement("Placemark");

                string nodeXML = @" <description>{1},{2:F6}{3:F6}{4:F2}</description>
      <name>{0}</name>
      <LookAt>
        <longitude>{1}</longitude>
        <latitude>{2}</latitude>
        <range>20000</range>
        <tilt>0</tilt>
        <heading>3</heading>
      </LookAt>
      <Point>
        <coordinates>{1},{2},0</coordinates>
      </Point>";

                Placemark.InnerXml = string.Format(nodeXML, item.Mark, longitudeValue, latitudeValue, Environment.NewLine, item.Distance);


                nodeFolder.AppendChild(Placemark);
            }
            xmlDoc.Save(txtTargetFile.Text.Trim());

            if (finError)
            {
                string cmdSql = "/select," + _errFilePath;
                System.Diagnostics.ProcessStartInfo psInfo = new System.Diagnostics.ProcessStartInfo("explorer", cmdSql);
                System.Diagnostics.Process.Start(psInfo);
            }
            else
            {
                ProcessStartInfo psi = new ProcessStartInfo("explorer", "/SELECT," + txtTargetFile.Text.Trim());
                Process.Start(psi);
            }


        }
        /// <summary>
        /// 计算平均值
        /// </summary>
        /// <param name="mDataLines"></param>
        /// <param name="avageLatitude"></param>
        /// <param name="avageLongitude"></param>
        private void CalculateAverage(List<GPGGAData> mDataLines, out decimal avageLatitude, out decimal avageLongitude)
        {
            decimal sumLatitude = 0.000000M;
            decimal sumLongitude = 0.000000M;
            foreach (var item in mDataLines)
            {
                sumLatitude += item.Latitude;
                sumLongitude += item.Longitude;
            }

            avageLatitude = sumLatitude / mDataLines.Count;
            avageLongitude = sumLongitude / mDataLines.Count;
        }
        /// <summary>
        /// 计算不精确的距离
        /// </summary>
        /// <param name="center"></param>
        /// <param name="point"></param>
        /// <returns></returns>
        private decimal Distance(GPGGAData center, GPGGAData point)
        {

            decimal a, b, R;
            R = 6378137; //地球半径
            a = (center.Latitude - point.Latitude) * (decimal)Math.PI / 180.0m;
            b = (center.Longitude - point.Longitude) * (decimal)Math.PI / 180.0m;
            decimal d;
            decimal sa2, sb2;
            sa2 = (decimal)Math.Sin((double)(a / 2.0m));
            sb2 = (decimal)Math.Sin((double)(b / 2.0m));
            d = 2 * R * (decimal)Math.Asin(Math.Sqrt((double)(sa2 * sa2 + (decimal)(Math.Cos((double)center.Latitude) * Math.Cos((double)point.Latitude)) * sb2 * sb2)));
            return d;
        }
        #endregion

    }

}
